use std::{env, fs};
use std::os::unix::fs::MetadataExt;
use std::path::Path;

use crate::runtime::runtime::error::InternalError;
use crate::runtime::runtime::result::InternalErrorResult;

/// 在路径范围内查找给定名称的可执行文件
/// 查找范围：
///     若name本身以/开始，则检查其是否是可执行文件，不再继续查找，返回结果
///     其次查找给定paths路径列表中是否存在名为name的可执行文件，若存在返回结果
///     最后查找环境变量$PATH中的所有路径中是否存在名为name的可执行文件，并返回最后结果
pub fn lock_path(name: &str, paths: &Vec<String>) -> InternalErrorResult<Option<String>> {
    if name.starts_with("/") {
        if !executable(name)? {
            return Ok(None);
        }

        return Ok(Some(name.to_string()));
    }

    let target = find_executable(name, paths)?;
    if target.is_some() {
        return Ok(target);
    }

    let os_path = env::var("PATH").map_err(|e| InternalError::new(format!("Could not get environment: {} {}", "PATH", e)))?;
    let paths: Vec<String> = Vec::from_iter(os_path.split(":").into_iter()).iter().map(|s| s.to_string()).collect();

    let target = find_executable(name, &paths)?;
    if target.is_some() {
        return Ok(target);
    }

    Ok(None)
}

/// 检查给定路径是否是可执行文件
fn executable(path: &str) -> InternalErrorResult<bool> {
    if !Path::new(path).exists() {
        return Ok(false);
    }

    let metadata = fs::metadata(path).map_err(|e| InternalError::new(format!("Could not check executable: {}", e)))?;
    if metadata.is_dir() || metadata.mode() & 0o111 == 0 {
        return Ok(false);
    }

    Ok(true)
}

/// 在给定的路径表中查找是否存在可执行的文件名
fn find_executable(name: &str, paths: &Vec<String>) -> InternalErrorResult<Option<String>> {
    for path in paths {
        let p = Path::new(path).join(name);
        let name = p.to_str().unwrap();

        if executable(name)? {
            return Ok(Some(name.to_string()));
        }
    }

    Ok(None)
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_executable() {
        let result = super::executable("/bin/sh");
        assert_eq!(true, result.is_ok(), "检查可执行文件失败: {}", result.unwrap_err());
        assert_eq!(true, result.unwrap(), "检查可执行文件失败，返回了错误的结果: {}", "/bin/sh");

        let result = super::executable("/bin/sh2");
        assert_eq!(true, result.is_ok(), "检查可执行文件失败: {}", result.unwrap_err());
        assert_eq!(false, result.unwrap(), "检查可执行文件失败，返回了错误的结果: {}", "/bin/sh2");

        let result = super::executable("/usr/lib/charset.alias");
        assert_eq!(true, result.is_ok(), "检查可执行文件失败: {}", result.unwrap_err());
        assert_eq!(false, result.unwrap(), "检查可执行文件失败，返回了错误的结果: {}", "/usr/lib/charset.alias");
    }

    #[test]
    fn test_find_executable() {
        let result = super::find_executable("sh", &Default::default());
        assert_eq!(true, result.is_ok(), "查找文件路径失败: {}", result.unwrap_err());

        let path = result.unwrap();
        assert_eq!(true, path.is_none(), "查找到了错误的结果: {}", path.unwrap());

        let paths: Vec<String> = vec!["/sbin".to_string(), "/bin".to_string(), "/usr/bin".to_string()];
        let result = super::find_executable("sh", &paths);
        assert_eq!(true, result.is_ok(), "查找文件路径失败: {}", result.unwrap_err());

        let path = result.unwrap();
        assert_eq!(true, path.is_some(), "未查找到结果: {}", "/bin/sh");

        let string = path.unwrap();
        assert_eq!("/bin/sh", string, "查找到错误的结果: {}", string);
    }

    #[test]
    fn test_lock_path() {
        let paths: Vec<String> = vec!["/opt/dem/bin".to_string()];
        let result = super::lock_path("dem", &paths);
        assert_eq!(true, result.is_ok(), "查找文件路径失败: {}", result.unwrap_err());

        let path = result.unwrap();
        assert_eq!(true, path.is_some(), "未查找到结果: {}", "/opt/dem/bin/dem");

        let string = path.unwrap();
        assert_eq!("/opt/dem/bin/dem", string, "查找到错误的结果: {}", string);

        let paths: Vec<String> = vec!["/opt/dem/bin".to_string()];
        let result = super::lock_path("sh", &paths);
        assert_eq!(true, result.is_ok(), "查找文件路径失败: {}", result.unwrap_err());

        let path = result.unwrap();
        assert_eq!(true, path.is_some(), "未查找到结果: {}", "/bin/sh");

        let string = path.unwrap();
        assert_eq!("/bin/sh", string, "查找到错误的结果: {}", string);

        let paths: Vec<String> = vec!["/opt/dem/bin".to_string()];
        let result = super::lock_path("sh2", &paths);
        assert_eq!(true, result.is_ok(), "查找文件路径失败: {}", result.unwrap_err());

        let path = result.unwrap();
        assert_eq!(false, path.is_some(), "查找到错误结果: {}", path.unwrap());
    }
}